package com.xqboss.apps.service.user;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.RedisKey;
import com.xqboss.apps.domain.consumer.ConsumerTeamGiveRecord;
import com.xqboss.apps.domain.equipment.Equipment;
import com.xqboss.apps.domain.equipment.EquipmentUpgradeConfigBreak;
import com.xqboss.apps.domain.member.SysUserNft;
import com.xqboss.apps.domain.nft.*;
import com.xqboss.apps.domain.order.OrderItem;
import com.xqboss.apps.domain.punch.PunchUserConfig;
import com.xqboss.apps.domain.punch.PunchWeakenConfig;
import com.xqboss.apps.domain.synthesis.SynthesisHoldCondition;
import com.xqboss.apps.domain.synthesis.SynthesisPlanConsume;
import com.xqboss.apps.dto.nft.NftTransferDto;
import com.xqboss.apps.dto.synthesis.SynthesisPlanConsumeDto;
import com.xqboss.apps.dto.user.AppUserNftListDto;
import com.xqboss.apps.dto.user.UserNftListDto;
import com.xqboss.apps.enums.market.MarketTypeEnum;
import com.xqboss.apps.enums.nft.*;
import com.xqboss.apps.enums.user.*;
import com.xqboss.apps.mapper.member.SysUserNftMapper;
import com.xqboss.apps.service.consumer.ConsumerTeamGiveRecordService;
import com.xqboss.apps.service.market.MarketService;
import com.xqboss.apps.service.nft.NftNumberService;
import com.xqboss.apps.service.nft.NftService;
import com.xqboss.apps.service.nft.NftTransferConfigService;
import com.xqboss.apps.service.nft.NftUsedRecordService;
import com.xqboss.apps.service.order.NftDispatchRecordService;
import com.xqboss.apps.service.punch.*;
import com.xqboss.apps.service.synthesis.SynthesisPlanConsumeService;
import com.xqboss.apps.vo.equipment.EquipmentInfoVo;
import com.xqboss.apps.vo.nft.NftTransferConfigVo;
import com.xqboss.apps.vo.punch.UserPunchEquipmentVo;
import com.xqboss.apps.vo.user.AppUserNFTItemVo;
import com.xqboss.apps.vo.user.UserNFTItemVo;
import com.xqboss.apps.vo.user.UserNFTVo;
import com.xqboss.chain.domain.chain.UpUpdateChain;
import com.xqboss.chain.enums.UpChainStatusEnum;
import com.xqboss.chain.enums.UpChainTypeEnum;
import com.xqboss.chain.service.UpUpdateChainService;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.AsyncHandlerUtil;
import com.xqboss.system.service.ISysConfigService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户藏品表
 * </p>
 *
 * @author Trent
 * @date 2023/8/25
 */
@Slf4j
@Service
public class SysUserNftService extends ServiceImpl<SysUserNftMapper, SysUserNft> {

    @Autowired
    private PunchUserConfigService punchUserConfigService;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private NftService nftService;

    @Autowired
    private NftNumberService nftNumberService;

    @Autowired
    private UpUpdateChainService upUpdateChainService;

    @Autowired
    private NftDispatchRecordService nftDispatchRecordService;

    @Lazy
    @Autowired
    private SynthesisPlanConsumeService synthesisService;

    @Autowired
    private NftUsedRecordService nftUsedRecordService;

    @Autowired
    private PunchUserRecordService punchUserRecordService;

    @Autowired
    private PunchRuleService punchRuleService;

    @Lazy
    @Autowired
    private MarketService marketService;

    @Autowired
    private UserPunchService userPunchService;
    @Autowired
    private NftTransferConfigService nftTransferConfigService;
    @Autowired
    private SysUserWalletService sysUserWalletService;
    @Autowired
    private UserAssetWrenchRecordService userAssetWrenchRecordService;
    @Autowired
    private UserAssetLidouService userAssetLidouService;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private ConsumerTeamGiveRecordService consumerTeamGiveRecordService;
    @Autowired
    private PunchWeakenConfigService punchWeakenConfigService;


    public UserNFTItemVo getNftItemInfo(Long userNftId, Long userId) {
        UserNFTItemVo nftItem = baseMapper.getNftInfoById(userNftId, userId);
        if (nftItem == null) {
            throw new ServiceException("典藏不存在！");
        }
        if (nftItem.getNftType() == NftTypeEnum.EQUIPMENT) {
            BigDecimal durableUseRatio = sysConfigService.getValue(Config.Sys.Punch.耐久消耗系数);
            BigDecimal breakageUse = sysConfigService.getValue(Config.Sys.Punch.消耗破损值);
            EquipmentInfoVo equipment = nftItem.getEquipment();
            equipment.setIsInsDurability(equipment.getDurability().compareTo(durableUseRatio.multiply(BigDecimal.valueOf(equipment.getTotalAttrCount())).setScale(2, RoundingMode.DOWN)) < 0);
            equipment.setIsBreakage(equipment.getBreakage().compareTo(breakageUse) < 0);
            equipment.setPunchAirdropBeans(nftService.getPunchAirdropBeans(nftItem.getNftId(), equipment.getLuck()));
            // 设置装备剩余空投与打卡次数
            equipment.setRemainAirdrop(userPunchService.calcPunchIncomeByWeaken(BeanUtil.copyProperties(equipment, Equipment.class), false));
            equipment.setRemainPunch(userPunchService.calcPunchCountByBreakage(equipment.getBreakage()));
        }
        return nftItem;
    }

    /**
     * 获取用户的藏品列表
     *
     * @return
     */
    public List<UserNFTVo> getNftGroupList(UserNftListDto params) {
        List<UserNFTVo> nftGroupList = baseMapper.getNftGroupList(params);
        // 检查是否需要获取转赠配置
        if (CollUtil.isNotEmpty(nftGroupList)) {
            nftGroupList.parallelStream().forEach(item -> checkTransferConfig(item, params.getUserId()));
        }
        return nftGroupList;
    }

    /**
     * 检测更新转赠配置
     *
     * @param item   UserNFTVo对象
     *               userId 用户ID
     */
    public void checkTransferConfig(UserNFTVo item, Long userId) {
        if (item.getNftType() == NftTypeEnum.NORMAL) {
            NftTransferConfig transferConfig = nftTransferConfigService.getTransferConfig(item.getNftId());
            if (Objects.nonNull(transferConfig)) {
                item.setTransferConfig(BeanUtil.copyProperties(transferConfig, NftTransferConfigVo.class));
                // 如果是白名单转赠，检测当前用户是否在白名单，如果是设为白名单
                if (item.getTransferConfig().getTransferScope() == 1) {
                    item.getTransferConfig().setIsWhite(transferConfig.getWhiteUser().contains(String.valueOf(userId)));
                } else {
                    // 如果是全部用户转赠，则所有用户都是白名单
                    item.getTransferConfig().setIsWhite(true);
                }
                // 如果设定转赠上限，设置用户可转次数 = 设定转赠上限 - 用户已转次数
                if (item.getTransferConfig().getTransferUpper() != null && item.getTransferConfig().getTransferUpper() > 0) {
                    int count = consumerTeamGiveRecordService.countByTransfer(userId, item.getNftId(), item.getNftType().getValue());
                    item.getTransferConfig().setTransferUpper(item.getTransferConfig().getTransferUpper() - count);
                }
            }
        }
    }





    //*******************************************************************************************************************
    //************************************************下面是APP装备铺封装***************************************************
    //*******************************************************************************************************************

    /**
     * 以藏品编号为单位获取藏品列表
     *
     * @return
     */
    @SneakyThrows
    public List<AppUserNFTItemVo> getAppNftNumberList(AppUserNftListDto params) {

        // 获取合成配置
        CompletableFuture<Void> a = AsyncHandlerUtil.run(() -> giveSynthsisPlanConsume(params));

        // 获取用户当前装备的装备
        CompletableFuture<PunchUserConfig> b = AsyncHandlerUtil.supplier(()->getPunchUserConfig(params));

        // 是否开启转卖
        CompletableFuture<Boolean> c = AsyncHandlerUtil.supplier(()->getOpenSell(params));

        // 打卡削弱配置集
        CompletableFuture<List<PunchWeakenConfig>> d =  AsyncHandlerUtil.supplier(this::getWeakenConfigs);

        // 线程等待
        CompletableFuture.allOf(a, b, c, d).join();

        PunchUserConfig config = b.get();
        Boolean openSell = c.get();
        List<PunchWeakenConfig> pWConfigs = d.get();

        //from db 主线程
        List<UserNFTItemVo> nftNumberList = baseMapper.getNftNumberList(params);

        // 配置藏品的耐久度不足状态、破损度不足状态、转卖状态、打卡空投热豆、剩余空投、剩余打卡次数
        BigDecimal durableUseRatio = sysConfigService.getValue(Config.Sys.Punch.耐久消耗系数);
        BigDecimal breakageUse = sysConfigService.getValue(Config.Sys.Punch.消耗破损值);
        BigDecimal defaultIncomeRatio = sysConfigService.getValue(Config.Sys.Punch.收益折算系数);
        List<Runnable> runnables = new ArrayList<>(nftNumberList.size()*8);
        for (UserNFTItemVo vo : nftNumberList) {
            if (vo.getNftType() == NftTypeEnum.EQUIPMENT) {
                EquipmentInfoVo equ = vo.getEquipment();

                runnables.add(()->equ.setIsInsDurability(isInsDurability(equ, durableUseRatio)));
                runnables.add(()->equ.setIsBreakage(equ.getBreakage().compareTo(breakageUse) < 0));
                runnables.add(()->setNftStatus(params, vo, config));
                runnables.add(()->equ.setPunchAirdropBeans(getPunchAirdropBeans(vo, equ)));
                runnables.add(()->equ.setRemainAirdrop(getRemainAirdrop(equ)));
                runnables.add(()->equ.setTotalAirdrop(getTotalAirdrop(equ)));
                runnables.add(()->equ.setRemainPunch(getRemainPunch(equ)));
                runnables.add(()->equ.setNextPunchAirdrop(getNextPunchAirdrop(equ,pWConfigs,defaultIncomeRatio)));
            }
            vo.setIsResell(openSell);
        }
        AsyncHandlerUtil.runAllJoin(runnables);

        //封装
        return change(nftNumberList);
    }

    // 打卡削弱配置
    private List<PunchWeakenConfig> getWeakenConfigs() {
         return punchWeakenConfigService.lambdaQuery().orderByAsc(PunchWeakenConfig::getDurabilityRate).list();
    }

    // 封装
    private List<AppUserNFTItemVo> change(List<UserNFTItemVo> nns) {
        List<AppUserNFTItemVo> appUserNFTItemVos = new ArrayList<>(nns.size());
        List<Runnable> runnables = new ArrayList<>(nns.size());
        nns.forEach(nn->runnables.add(()->copyPro(nn, appUserNFTItemVos)));
        AsyncHandlerUtil.runAllJoin(runnables);
        return appUserNFTItemVos;
    }

    private static void copyPro(UserNFTItemVo nn, List<AppUserNFTItemVo> appUserNFTItemVos) {
        AppUserNFTItemVo vo = BeanUtil.copyProperties(nn, AppUserNFTItemVo.class);
        BeanUtil.copyProperties(nn.getEquipment(),vo);
        appUserNFTItemVos.add(vo);
    }

    private void giveSynthsisPlanConsume(AppUserNftListDto params) {
        if(NftTypeEnum.EQUIPMENT == params.getNftType()){
            if(Objects.nonNull(params.getSynthesisConsumeId())){
                SynthesisPlanConsume consume = synthesisService.getConsume(params.getSynthesisConsumeId());
                params.setLevelThreshold(consume.getLevelThreshold());
                params.setDurableThreshold(consume.getDurableThreshold());
                params.setBreakageThreshold(consume.getBreakageThreshold());
                params.setMinLevel(consume.getMinLevel());
                params.setMaxLevel(consume.getMaxLevel());
                params.setMinDurable(consume.getMinDurable());
                params.setMaxDurable(consume.getMaxDurable());
                params.setMinBreakage(consume.getMinBreakage());
                params.setMaxBreakage(consume.getMaxBreakage());
            }
        }else{
            params.setLevelThreshold(null);
            params.setDurableThreshold(null);
            params.setBreakageThreshold(null);
        }
    }

    private PunchUserConfig getPunchUserConfig(AppUserNftListDto params) {
        PunchUserConfig config = punchUserConfigService.getConfig(params.getUserId());
        if (BooleanUtil.isTrue(params.getSortByEquip())) {
            if (Objects.nonNull(params.getUserId())) {
                params.setFirstUserNftId(config.getUserNftId());
            }
        }
        return config;
    }

    private Boolean getOpenSell(AppUserNftListDto params) {
        boolean openSell = true;
        if (Objects.nonNull(params.getNftId())) {
            Nft nft = nftService.getNft(params.getNftId());
            if (Objects.nonNull(nft)) {
                openSell = nft.getIsResell() == ResellEnum.OPEN;
            }
        }
        return openSell;
    }

    private int getRemainPunch(EquipmentInfoVo equ) {
        return userPunchService.calcPunchCountByBreakage(equ.getBreakage());
    }

    private BigDecimal getRemainAirdrop(EquipmentInfoVo equ) {
        return userPunchService.calcPunchIncomeByWeaken(BeanUtil.copyProperties(equ, Equipment.class), false);
    }

    private BigDecimal getTotalAirdrop(EquipmentInfoVo equ) {
        return userPunchService.calcPunchIncomeByWeaken(BeanUtil.copyProperties(equ, Equipment.class), true);
    }

    private BigDecimal getNextPunchAirdrop(EquipmentInfoVo equ,List<PunchWeakenConfig> pWConfigs,BigDecimal defaultIncomeRatio) {
        PunchWeakenConfig weakenConfig = pWConfigs.stream()
                .filter(t -> t.getDurabilityRate().compareTo(equ.getDurability().divide(equ.getTotalDurability(), 2, RoundingMode.UP)) >= 0)
                .findFirst()
                .orElse(null);
        return userPunchService.calcPunchIncome(BeanUtil.copyProperties(equ, Equipment.class),defaultIncomeRatio, weakenConfig,true);
    }

    private BigDecimal getPunchAirdropBeans(UserNFTItemVo userNFTItemVo, EquipmentInfoVo equ) {
        return nftService.getPunchAirdropBeans(userNFTItemVo.getNftId(), equ.getLuck());
    }

    private static boolean isInsDurability(EquipmentInfoVo equ, BigDecimal durableUseRatio) {
        return equ.getDurability().compareTo(durableUseRatio.multiply(BigDecimal.valueOf(equ.getTotalAttrCount())).setScale(2, RoundingMode.DOWN)) < 0;
    }

    private void setNftStatus(AppUserNftListDto params, UserNFTItemVo userNFTItemVo, PunchUserConfig config) {
        //检查装备是否在打卡时间段使用过，打完一件装备最高次数了就设为使用过状态
        long nftPunchCount = punchUserRecordService.countUserRuleNftNumberIdPunch(params.getUserId(), config.getPunchRuleId(), userNFTItemVo.getNftNumberId(), LocalDate.now());
        if (userNFTItemVo.getNftStatus() != UserNftStatusEnum.USED
                && nftPunchCount > 0
                && nftPunchCount == (long) punchRuleService.requireSaleableRule(config.getPunchRuleId()).getPunchCount()) {
            userNFTItemVo.setNftStatus(UserNftStatusEnum.USED_PASS);
        }
    }


    //*******************************************************************************************************************
    //************************************************上面是APP装备铺封装***************************************************
    //*******************************************************************************************************************







    /**
     * 以藏品编号为单位获取藏品列表
     *
     * @return
     */
    public List<UserNFTItemVo> getNftNumberList(UserNftListDto params) {
        if(NftTypeEnum.EQUIPMENT == params.getNftType()){
            if(Objects.nonNull(params.getSynthesisConsumeId())){
                SynthesisPlanConsume consume = synthesisService.getConsume(params.getSynthesisConsumeId());
                params.setLevelThreshold(consume.getLevelThreshold());
                params.setDurableThreshold(consume.getDurableThreshold());
                params.setBreakageThreshold(consume.getBreakageThreshold());
                params.setMinLevel(consume.getMinLevel());
                params.setMaxLevel(consume.getMaxLevel());
                params.setMinDurable(consume.getMinDurable());
                params.setMaxDurable(consume.getMaxDurable());
                params.setMinBreakage(consume.getMinBreakage());
                params.setMaxBreakage(consume.getMaxBreakage());
            }
        }else{
            params.setLevelThreshold(null);
            params.setDurableThreshold(null);
            params.setBreakageThreshold(null);
        }

        // 获取用户当前装备的装备
        PunchUserConfig config = punchUserConfigService.getConfig(params.getUserId());
        if (BooleanUtil.isTrue(params.getSortByEquip())) {
            if (Objects.nonNull(params.getUserId())) {
                params.setFirstUserNftId(config.getUserNftId());
            }
        }
        params.startPageAndOrder();

        List<UserNFTItemVo> nftNumberList = baseMapper.getNftNumberList(params);
        BigDecimal durableUseRatio = sysConfigService.getValue(Config.Sys.Punch.耐久消耗系数);
        BigDecimal breakageUse = sysConfigService.getValue(Config.Sys.Punch.消耗破损值);

        Boolean openSell = true;
        if (Objects.nonNull(params.getNftId())) {
            Nft nft = nftService.getNft(params.getNftId());
            if (Objects.nonNull(nft)) {
                openSell = nft.getIsResell() == ResellEnum.OPEN;
            }
        }

        // 配置藏品的耐久度不足状态、破损度不足状态、转卖状态
        for (UserNFTItemVo userNFTItemVo : nftNumberList) {
            if (userNFTItemVo.getNftType() == NftTypeEnum.EQUIPMENT) {
                EquipmentInfoVo equipment = userNFTItemVo.getEquipment();
                equipment.setIsInsDurability(equipment.getDurability().compareTo(durableUseRatio.multiply(BigDecimal.valueOf(equipment.getTotalAttrCount())).setScale(2, RoundingMode.DOWN)) < 0);
                equipment.setIsBreakage(equipment.getBreakage().compareTo(breakageUse) < 0);
                //检查装备是否在打卡时间段使用过，打完一件装备最高次数了就设为使用过状态
                long nftPunchCount = punchUserRecordService.countUserRuleNftNumberIdPunch(params.getUserId(), config.getPunchRuleId(), userNFTItemVo.getNftNumberId(), LocalDate.now());
                if (userNFTItemVo.getNftStatus() != UserNftStatusEnum.USED
                        && nftPunchCount > 0
                        && nftPunchCount == (long) punchRuleService.requireSaleableRule(config.getPunchRuleId()).getPunchCount()) {
                    userNFTItemVo.setNftStatus(UserNftStatusEnum.USED_PASS);
                }
//                equipment.setPunchAirdropBeans(nftService.getPunchAirdropBeans(userNFTItemVo.getNftId(), equipment.getLuck()));
//                // 设置装备剩余空投与打卡次数
//                equipment.setRemainAirdrop(userPunchService.calcPunchIncomeByWeaken(BeanUtil.copyProperties(equipment, Equipment.class), false));
//                equipment.setRemainPunch(userPunchService.calcPunchCountByBreakage(equipment.getBreakage()));
            }
            userNFTItemVo.setIsResell(openSell);
        }
        return nftNumberList;
    }

    /**
     * 获取用户背包装备
     * @param userId
     * @return
     */
    public List<UserPunchEquipmentVo> knapsackEquipmentList(Long userId) {
        List<UserPunchEquipmentVo> equipmentList = baseMapper.equipmentList(userId, UserNftStatusEnum.NORMAL.getValue());
        if (CollUtil.isNotEmpty(equipmentList)) {
            // 批量计算剩余空投
            this.calcPunchIncomeBatch(equipmentList);
            // 耐久度削弱比列
            List<PunchWeakenConfig> weakenConfigList = punchWeakenConfigService.lambdaQuery()
                    .orderByAsc(PunchWeakenConfig::getDurabilityRate)
                    .list();
            // 配置的系数
            BigDecimal durableUseRatio = sysConfigService.getValue(Config.Sys.Punch.耐久消耗系数);
            BigDecimal breakageUse = sysConfigService.getValue(Config.Sys.Punch.消耗破损值);
            Integer punchCount = sysConfigService.getValue(Config.Sys.Team.单人当日可打卡次数);
            BigDecimal defaultIncomeRatio = sysConfigService.getValue(Config.Sys.Punch.收益折算系数);

            equipmentList.parallelStream().forEach(equipment -> {
                equipment.setIsInsDurability(equipment.getDurability().compareTo(durableUseRatio.multiply(BigDecimal.valueOf(equipment.getTotalAttrCount())).setScale(2, RoundingMode.DOWN)) < 0);
                equipment.setIsBreakage(equipment.getBreakage().compareTo(breakageUse) < 0);
                // 设置装备剩余空投与打卡次数
                equipment.setRemainPunch(equipment.getBreakage().divide(breakageUse, 0, RoundingMode.DOWN).intValue());
                // 大于等于当前耐久度的最小耐久度限制
                // 如：当前耐久度为80， 配置里有90、80、70， 则取80
                PunchWeakenConfig weakenConfig = weakenConfigList.stream()
                        .filter(t -> t.getDurabilityRate().compareTo(equipment.getDurability().divide(equipment.getTotalDurability(), 2, RoundingMode.UP)) >= 0)
                        .findFirst()
                        .orElse(null);
                equipment.setNextPunchWeaken(weakenConfig);
                // 装备今日剩余打卡次数 = 打卡规则数 - 今日已打卡次数
                equipment.setTodayRemainPunch(punchCount - equipment.getTodayAlreadyPunch());
                // 下次空投值
                equipment.setPunchAirdropBeans(userPunchService.calcPunchIncome(BeanUtil.copyProperties(equipment, Equipment.class), defaultIncomeRatio, weakenConfig, true));
            });
            // 默认排序按照equipmentList中剩余空投remainAirdrop越大排在前面
            equipmentList.sort((o1, o2) -> o2.getRemainAirdrop().compareTo(o1.getRemainAirdrop()));
            // 删除今日剩余打卡次数为零的装备
            equipmentList.removeIf(e -> e.getTodayRemainPunch() <= 0);
        }
        return equipmentList;
    }

    /**
     * 批量计算空投
     * @param equipmentList
     */
    private void calcPunchIncomeBatch(List<UserPunchEquipmentVo> equipmentList) {
        // 批量计算剩余空投
        List<Equipment> equipment = BeanUtil.copyToList(equipmentList, Equipment.class);
        userPunchService.calcPunchIncomeByWeakenBatch(equipment, false);
        // 单独拷贝剩余空投到equipmentList
        for (int i = 0; i < equipmentList.size(); i++) {
            equipmentList.get(i).setRemainAirdrop(equipment.get(i).getRemainingAirdrop());
        }
        // 批量计算总空投
        List<Equipment> equ2 = BeanUtil.copyToList(equipmentList, Equipment.class);
        userPunchService.calcPunchIncomeByWeakenBatch(equ2, true);
        // 单独拷贝总空投到equipmentList
        for (int i = 0; i < equipmentList.size(); i++) {
            equipmentList.get(i).setTotalAirdrop(equ2.get(i).getRemainingAirdrop());
        }
    }

    /**
     * 1
     * 给用户分配NFT
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public List<SysUserNft> addUserNft(Long userId, UserNftSourceTypeEnum source, Long orderMainId, Long orderItemId, Nft nft, List<NftNumber> nftNumbers, LocalDateTime saleableTime) {

        List<SysUserNft> userNftList = new ArrayList<>();
        for (NftNumber nftNumber : nftNumbers) {
            SysUserNft userNft = new SysUserNft();
            userNft.setId(IdUtil.getSnowflakeNextId());
            userNft.setUserId(userId);
            userNft.setNftType(nft.getNftType());
            userNft.setNftId(nftNumber.getNftId());
            userNft.setOrderMainId(orderMainId);
            userNft.setNftStatus(UserNftStatusEnum.NORMAL);
            userNft.setOrderItemId(orderItemId);
            userNft.setNftNumberId(nftNumber.getId());
            userNft.setSource(source);
            userNft.setSaleableTime(saleableTime);

            userNft.setNftNumber(nftNumber);
            userNftList.add(userNft);
        }
        boolean saved = saveBatch(userNftList);
        if (!saved) {
            throw new ServiceException("存储失败！");
        }

        return userNftList;
    }

    /**
     * 1
     * 给用户分配NFT
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public SysUserNft addUserNft(Long userId, UserNftSourceTypeEnum source, Long orderMainId, Long orderItemId, NftTypeEnum nftType, Long nftId, Long nftNumberId, LocalDateTime saleableTime) {
        SysUserNft userNft = new SysUserNft();
        userNft.setId(IdUtil.getSnowflakeNextId());
        userNft.setUserId(userId);
        userNft.setNftType(nftType);
        userNft.setNftId(nftId);
        userNft.setOrderMainId(orderMainId);
        userNft.setNftStatus(UserNftStatusEnum.NORMAL);
        userNft.setOrderItemId(orderItemId);
        userNft.setNftNumberId(nftNumberId);
        userNft.setSource(source);
        userNft.setSaleableTime(saleableTime);
        boolean saved = save(userNft);
        if (!saved) {
            throw new ServiceException("存储失败！");
        }

        return userNft;
    }

    /**
     * 更新装备状态
     *
     * @param userNftId
     * @param status
     * @return
     */
    public boolean updateStatus(Long userNftId, UserNftStatusEnum status) {
        return update(new LambdaUpdateWrapper<SysUserNft>()
                .set(SysUserNft::getNftStatus, status)
                .eq(SysUserNft::getId, userNftId));
    }

    /**
     * 下架打卡规则，重置选择该打卡规则的用户的装备状态
     *
     * @param punchRuleId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public long cleanPunchRule(Long punchRuleId) {
        return baseMapper.resetNftStatusByPunchRuleId(punchRuleId);
    }

    /**
     * 转移
     *
     * @param sourceId
     * @param sellerUserId
     * @param buyerUserId
     * @param isGift
     */
    @Transactional(rollbackFor = Exception.class)
    public void moveUserNftToUser(OrderItem orderItem, Long sourceId, Long sellerUserId, Long buyerUserId, boolean isGift) {
        // 存储分发记录
        SysUserNft oldUserNft = getById(sourceId);
        if (Objects.isNull(oldUserNft)) {
            log.error("转移NFT失败，用户藏品不存在, userNftId:{}", sourceId);
            throw new ServiceException("交易失败！");
        }
        if (oldUserNft.getNftStatus() != UserNftStatusEnum.SALEABLE) {
            log.error("转移NFT失败，用户藏品状态异常！userNftId:{}, status:{}", sourceId, oldUserNft.getNftStatus());
            throw new ServiceException("交易失败！");
        }
        if (!Objects.equals(oldUserNft.getUserId(), sellerUserId)) {
            log.error("转移NFT失败，用户ID不对！userNftId:{}, userId:{}", sourceId, sellerUserId);
            throw new ServiceException("交易失败！");
        }
        boolean remove = update(Wrappers.lambdaUpdate(SysUserNft.class)
                .set(SysUserNft::getDelFlag, true)
                .set(SysUserNft::getNftStatus, UserNftStatusEnum.SOLD_OUT)
                .eq(SysUserNft::getId, sourceId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.SALEABLE));
        if(!remove){
            log.error("转移NFT失败，删除用户藏品失败, userNftId:{}", sourceId);
            throw new ServiceException("交易失败！");
        }
        // 市场转卖， 冷却时间跟着典藏走
        Nft nft = nftService.requireNft(oldUserNft.getNftId());
        LocalDateTime saleableTime = LocalDateTime.now().plusMinutes(nft.getCoolingTime());
        SysUserNft newUserNft = addUserNft(buyerUserId, isGift ? UserNftSourceTypeEnum.GIFT : UserNftSourceTypeEnum.MARKET, orderItem.getOrderMainId(), orderItem.getId(), oldUserNft.getNftType(), oldUserNft.getNftId(), oldUserNft.getNftNumberId(), saleableTime);
        // 添加NFT交易记录
        nftDispatchRecordService.addMarketRecord(orderItem, newUserNft);
        // 添加上链表
        NftNumber nftNumber = nftNumberService.getNftNumber(newUserNft.getNftNumberId());
        nftNumberService.changeHoldUser(nftNumber.getId(), buyerUserId);
        upUpdateChainService.addData(newUserNft.getId(), nft.getContract(), nft.getId(), newUserNft.getNftNumberId(), nftNumber.getNumberValue(), newUserNft.getNftType().getValue(), oldUserNft.getUserId(), newUserNft.getUserId(), UpChainTypeEnum.TRANSACTION);
    }

    /**
     * 回收空投转移
     *
     * @param sourceId
     * @param fromUserId
     * @param toUserId
     * @param airRecordId
     * @param remarkId 备注来源id
     */
    @Transactional(rollbackFor = Exception.class)
    public void moveUserNftToUser(Long sourceId, Long fromUserId, Long toUserId, Long airRecordId, UserNftSourceTypeEnum sourceEnum, NftDispatchSourceEnum dispatchEnum, Long remarkId) {
        // 存储分发记录
        SysUserNft oldUserNft = getById(sourceId);
        if (Objects.isNull(oldUserNft)) {
            log.error("转移NFT失败，用户藏品不存在, userNftId:{}", sourceId);
            throw new ServiceException("交易失败！");
        }
        if (oldUserNft.getNftStatus() != UserNftStatusEnum.NORMAL) {
            log.error("转移NFT失败，用户藏品状态异常！userNftId:{}, status:{}", sourceId, oldUserNft.getNftStatus());
            throw new ServiceException("转移NFT失败，用户藏品状态异常！");
        }
        if (!Objects.equals(oldUserNft.getUserId(), fromUserId)) {
            log.error("转移NFT失败，用户ID不对！userNftId:{}, userId:{}", sourceId, fromUserId);
            throw new ServiceException("转移NFT失败，用户ID不对！");
        }
        boolean remove = update(Wrappers.lambdaUpdate(SysUserNft.class)
                .set(SysUserNft::getDelFlag, true)
                .set(SysUserNft::getNftStatus, UserNftStatusEnum.RECYCLE)
                .eq(SysUserNft::getId, sourceId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL));
        if(!remove){
            log.error("转移NFT失败，删除用户藏品失败, userNftId:{}", sourceId);
            throw new ServiceException("交易失败！");
        }
        // 冷却时间跟着典藏走
        Nft nft = nftService.requireNft(oldUserNft.getNftId());
        LocalDateTime saleableTime = LocalDateTime.now().plusMinutes(nft.getCoolingTime());
        SysUserNft newUserNft = addUserNft(toUserId, sourceEnum, oldUserNft.getOrderMainId(), oldUserNft.getId(), oldUserNft.getNftType(), oldUserNft.getNftId(), oldUserNft.getNftNumberId(), saleableTime);
        // 添加NFT交易记录
        NftDispatchRecord record = new NftDispatchRecord();
        record.setDispatchSource(dispatchEnum);
        record.setUserNftId(newUserNft.getId());
        record.setUserId(newUserNft.getUserId());
        record.setSourceId(remarkId);
        record.setOrderMainId(oldUserNft.getOrderMainId());
        record.setOrderItemId(airRecordId);
        record.setNftId(newUserNft.getNftId());
        record.setNftNumberId(newUserNft.getNftNumberId());
        record.setNftType(newUserNft.getNftType());
        nftDispatchRecordService.save(record);
        // 添加上链表
        NftNumber nftNumber = nftNumberService.getNftNumber(newUserNft.getNftNumberId());
        nftNumberService.changeHoldUser(nftNumber.getId(), toUserId);
        upUpdateChainService.addData(newUserNft.getId(), nft.getContract(), nft.getId(), newUserNft.getNftNumberId(), nftNumber.getNumberValue(), newUserNft.getNftType().getValue(), oldUserNft.getUserId(), newUserNft.getUserId(), UpChainTypeEnum.TRANSACTION);
    }

    /**
     * @param id
     * @return
     */
    public SysUserNft requireById(Long id, NftTypeEnum nftType, Long userId) {
        SysUserNft userNft = getById(id);
        if (userNft == null || !Objects.equals(userNft.getNftType(), nftType) || !Objects.equals(userNft.getUserId(), userId)) {
            if (nftType == NftTypeEnum.NORMAL) {
                throw new ServiceException("典藏不存在！");
            } else if (nftType == NftTypeEnum.EQUIPMENT) {
                throw new ServiceException("装备不存在！");
            }
        }
        return userNft;
    }

    /**
     * 通过藏品编号获取用户藏品
     *
     * @param nftNumberId 藏品编号ID
     * @return
     */
    public SysUserNft getUserNftByNftNumberId(Long nftNumberId) {
        return getOne(new LambdaQueryWrapper<SysUserNft>()
                .eq(SysUserNft::getNftNumberId, nftNumberId));
    }

    /**
     * 检测用户是否持有某藏品
     *
     * @param userId
     * @param nftId
     * @return
     */
    public Boolean checkHoldByNtfId(Long userId, Long nftId) {
        SysUserNft one = getOne(Wrappers.lambdaQuery(SysUserNft.class)
                .select(SysUserNft::getId)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftId, nftId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .last("LIMIT 1"));
        return Objects.nonNull(one);
    }


    /**
     * 检测NFT列表是否属于用户并且状态为闲置
     *
     * @param userNftIdList
     * @param userId
     * @return
     */
    public int countHoldNftList(Long userId, Long nftId, List<Long> userNftIdList) {
        LambdaQueryWrapper<SysUserNft> wrapper = Wrappers.lambdaQuery(SysUserNft.class)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .eq(SysUserNft::getNftId, nftId);
        if(Objects.nonNull(userNftIdList)){
                wrapper.in(SysUserNft::getId, userNftIdList);
        }
        return (int) count(wrapper);
    }

    /**
     * 检测NFT列表是否属于用户并且状态为闲置
     *
     * @param userId
     * @return
     */
    public int countHoldNftListByDate(Long userId, Long nftId, LocalDateTime date) {
        LambdaQueryWrapper<SysUserNft> wrapper = Wrappers.lambdaQuery(SysUserNft.class)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .le(SysUserNft::getCreateTime, date)
                .eq(SysUserNft::getNftId, nftId);
        return (int) count(wrapper);
    }

    /**
     * 检测NFT列表是否属于用户并且状态为闲置
     *
     * @param userId
     * @param consume
     * @param nftId
     * @return
     */
    public int countHoldEquipmentList(Long userId, Long nftId, SynthesisHoldCondition consume, LocalDateTime dateTime) {
        return baseMapper.countEquipment(userId, nftId, null, null, null, null,
                consume.getMinDurable(), consume.getMaxDurable(), consume.getMinBreakage(), consume.getMaxBreakage(), consume.getMinLevel(), consume.getMaxLevel(), dateTime);
    }

    /**
     * 检测NFT列表是否属于用户并且状态为闲置
     *
     * @param userId
     * @param consume
     * @param dateTime
     * @return
     */
    public int countHoldEquipmentList(Long userId, SynthesisPlanConsumeDto consume, Long nftId, LocalDateTime dateTime) {
        return baseMapper.countEquipment(userId, nftId, null, consume.getLevelThreshold(), consume.getBreakageThreshold(), consume.getDurableThreshold(),
                consume.getMinDurable(), consume.getMaxDurable(), consume.getMinBreakage(), consume.getMaxBreakage(), consume.getMinLevel(), consume.getMaxLevel(), dateTime);
    }

    /**
     * 统一销毁
     *
     * @param userId
     * @param userNftIdList
     * @param status
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public List<SysUserNft> userNftBurn(Long userId, List<Long> userNftIdList, UserNftStatusEnum status) {
        List<SysUserNft> list = list(Wrappers.lambdaQuery(SysUserNft.class)
                .select(SysUserNft::getId, SysUserNft::getNftId, SysUserNft::getNftNumberId, SysUserNft::getNftType, SysUserNft::getUserId)
                .eq(SysUserNft::getUserId, userId)
                .in(SysUserNft::getId, userNftIdList));
        if(list.size() != userNftIdList.size()){
            throw new ServiceException("藏品不存在！");
        }
        burnUserNft(userId, userNftIdList, status);
        Map<Long, Nft> nftMap = new HashMap<>();
        for (SysUserNft sysUserNft : list) {
            Nft nft = nftMap.get(sysUserNft.getNftId());
            if (nft == null) {
                nft = nftService.getNft(sysUserNft.getNftId());
                nftMap.put(nft.getId(), nft);
            }
            NftNumber nftNumber = nftNumberService.getNftNumber(sysUserNft.getNftNumberId());
            nftNumberService.setNftNumberBurn(nftNumber);
            upUpdateChainService.addData(sysUserNft.getId(), nft.getContract(), nft.getId(), sysUserNft.getNftNumberId(),
                    nftNumber.getNumberValue(), sysUserNft.getNftType().getValue(),
                    sysUserNft.getUserId(), null, UpChainTypeEnum.BURN);
        }
        return list;
    }

    /**
     * 销毁用户nft
     * @param userId
     * @param userNftIdList
     * @param status
     */
    @Transactional(rollbackFor = Exception.class)
    public void burnUserNft(Long userId, List<Long> userNftIdList, UserNftStatusEnum status){
        int count = baseMapper.update(null, Wrappers.lambdaUpdate(SysUserNft.class)
                .in(SysUserNft::getId, userNftIdList)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .set(SysUserNft::getNftStatus, status)
                .set(SysUserNft::getDelFlag, true)
        );
        if (count != userNftIdList.size()) {
            throw new ServiceException("操作失败！");
        }
    }

    /**
     * 当前用户是否有对应的 身份卡藏品 | 碎片藏品
     * @param nftId 藏品id(身份卡 或 碎片)
     * @param userId 用户id
     * @return true-对应的 身份卡藏品 | 碎片藏品
     */
    public Boolean isHaveNft(Long nftId, Long userId) {
        //不为空 且 藏品状态为未使用
        return getAnyNftNumber(nftId,userId) != null;
    }

    /**
     * 当前用户是否有对应的 身份卡藏品 | 碎片藏品
     * @param nftId 藏品id(身份卡 或 碎片)
     * @param userId 用户id
     * @return 如果有多个藏品返回最先拥有的那个
     */
    public Long getAnyNftNumber(Long nftId, Long userId) {
        if (nftId == null) {
            return null;
        }
        LambdaQueryWrapper<SysUserNft> wrapper = new LambdaQueryWrapper<SysUserNft>()
                .select(SysUserNft::getNftNumberId)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftId, nftId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL.getValue())
                .orderByDesc(SysUserNft::getCreateTime)
                .last("limit 1");
        SysUserNft nft = this.getOne(wrapper);
        if (nft==null) {
            return null;
        }
        return nft.getNftNumberId();
    }

    /**
     * 获取指定数量的nft number的id
     *
     * @param nftId 藏品id
     * @param userId 用户id
     * @param num 指定数量
     * @return
     */
    public Long[] getAppointNumNftNumber(Long nftId, Long userId, int num) {
        if (nftId == null) {
            return null;
        }
        if (num <= 0) {
            throw new ServiceException("指定数量必须大于等于1~");
        }
        LambdaQueryWrapper<SysUserNft> wrapper = new LambdaQueryWrapper<SysUserNft>()
                .select(SysUserNft::getNftNumberId)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftId, nftId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL.getValue())
                .orderByDesc(SysUserNft::getCreateTime)
                .last("limit "+num);
        List<SysUserNft> list = this.list(wrapper);
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        return list.stream().map(SysUserNft::getNftNumberId).toArray(value -> new Long[list.size()]);
    }


    /**
     * （身份卡 或 碎片）升级销毁——身份升级
     *
     * 更改当前藏品（身份卡 或 碎片）的状态为 升级销毁，并把删除状态置为已删除
     *
     * @param userId 用户id
     * @param nftNumberId 藏品numberId
     */
    @Transactional
    public void identityPromotionDestruction(Long userId, Long nftNumberId) {
        this.commonDestruction(userId, UserNftStatusEnum.IDENTITY_BURN, nftNumberId);
        // 插入使用记录
        NftUsedRecord nftUsedRecord = new NftUsedRecord();
        nftUsedRecord.setUserId(userId);
        nftUsedRecord.setNftNumberId(nftNumberId);
        nftUsedRecordService.save(nftUsedRecord);
    }


    /**
     * 藏品销毁 _ 通用 —— 批量
     *
     * 更改当前（装备 或 藏品）的状态为 队伍升级销毁，并把删除状态置为已删除
     *
     * @param userId 用户id
     * @param nftNumberIds 藏品numberId集 (+++++++++>>>>>> 要求属于同一个 nft)
     * @param enums 藏品消耗类型
     */
    @Transactional
    public void commonDestruction(Long userId,UserNftStatusEnum enums, Long... nftNumberIds) {
        Assert.notNull(userId,()->new ServiceException("用户id不能为空~"));
        Assert.notNull(nftNumberIds,()->new ServiceException("NFT编号ID不能为空~"));
        Assert.notNull(enums,()->new ServiceException("持有状态不能为空~"));
        Assert.isTrue(nftNumberIds.length>0,()->new ServiceException("NFT编号ID数量必须大于1~"));

        //校验
        List<SysUserNft> userNfts = this.lambdaQuery().select(SysUserNft::getId, SysUserNft::getNftId, SysUserNft::getNftNumberId, SysUserNft::getNftType, SysUserNft::getUserId)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .in(SysUserNft::getNftNumberId, nftNumberIds)
                .list();
        if (CollUtil.isEmpty(userNfts) || ( CollUtil.isNotEmpty(userNfts) && userNfts.size()!=nftNumberIds.length )) {
            throw new ServiceException("有藏品不存在或者非闲置状态！");
        }
        List<NftNumber> nftNumbers = this.nftNumberService.lambdaQuery().in(NftNumber::getId, nftNumberIds).list();
        if (CollUtil.isEmpty(nftNumbers) || ( CollUtil.isNotEmpty(nftNumbers) && nftNumbers.size()!=nftNumberIds.length )) {
            throw new ServiceException("有藏品不存在或者非闲置状态~");
        }

        //批量销毁userNft
        Long[] ids = userNfts.stream().map(SysUserNft::getId).toArray(value -> new Long[userNfts.size()]);
        boolean update = this.lambdaUpdate()
                .set(SysUserNft::getNftStatus, enums)
                .set(SysUserNft::getDelFlag, Boolean.TRUE)
                .in(SysUserNft::getId, ids)
                .update();
        if(!update){
            throw new ServiceException("有藏品不存在或者非闲置状态！");
        }
        //批量销毁nftNumberId
        update = this.nftNumberService.lambdaUpdate()
                .set(NftNumber::getDataEnable, false)
                .set(NftNumber::getIsGrant, NftNumberGrantStatusEnum.BURN)
                .set(NftNumber::getUserId, null)
                .in(NftNumber::getId, nftNumberIds)
                .update();
        if(!update){
            throw new ServiceException("有藏品不存在或者非闲置状态~");
        }

        //批量上链
        Nft nft = this.nftService.getById(userNfts.get(0).getNftId());
        Map<Long, NftNumber> numberMap = nftNumbers.stream().collect(Collectors.toMap(NftNumber::getId, Function.identity()));
        List<UpUpdateChain> chains = new ArrayList<>(nftNumberIds.length);
        UpUpdateChain upUpdateChain = null;
        for (SysUserNft userNft : userNfts) {
            upUpdateChain = new UpUpdateChain();
            upUpdateChain.setUserNftId(userNft.getId());
            upUpdateChain.setContract(nft.getContract());
            upUpdateChain.setNftId(nft.getId());
            upUpdateChain.setNftNumberId(userNft.getNftNumberId());
            upUpdateChain.setNumberValue(numberMap.get(userNft.getNftNumberId()).getNumberValue());
            upUpdateChain.setNftType(userNft.getNftType().getValue());
            upUpdateChain.setFromUserId(userNft.getUserId());
            upUpdateChain.setToUserId(null);
            upUpdateChain.setTxType(UpChainTypeEnum.BURN);
            upUpdateChain.setTxStatus(UpChainStatusEnum.NORMAL);
        }
        this.upUpdateChainService.saveBatch(chains);
    }







    /**
     * 返回藏品数量
     * @param nftId 藏品id
     * @param userId 用户id
     * @return 藏品数量
     */
    public Long getNftNumberCounts(Long nftId, Long userId) {
        if (nftId == null) {
            return 0l;
        }
        LambdaQueryWrapper<SysUserNft> wrapper = new LambdaQueryWrapper<SysUserNft>()
                .select(SysUserNft::getNftNumberId)
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftId, nftId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL.getValue());
        return this.count(wrapper);
    }

    /**
     * 获取 商品类型为宝盒 的分类数据
     *
     * @param params params
     * @return list
     */
    public List<UserNFTItemVo> getNftNumberBoxList(UserNftListDto params) {
        params.startPageAndOrder();
        return baseMapper.getBoxNftNumberList(params);
    }

    /**
     * 锁定/解锁
     * @param id
     */
    @Transactional
    public void updateLock(Long id, int isLock) {
        // 取消转卖和取消打卡
        if (isLock == 1) {
            SysUserNft userNft = getById(id);
            switch (userNft.getNftStatus()) {
                case SALEABLE:
                    marketService.offShelf(MarketTypeEnum.valueOf(userNft.getNftType().getValue()), id);
                    break;
                case USED:
                    PunchUserConfig config = punchUserConfigService.getConfig(userNft.getUserId());
                    if (config.getNftNumberId().equals(userNft.getNftNumberId())) {
                        this.updateStatus(config.getUserNftId(), UserNftStatusEnum.NORMAL);
                        punchUserConfigService.lambdaUpdate()
                                .set(PunchUserConfig::getPunchRuleId, null)
                                .set(PunchUserConfig::getNftNumberId, null)
                                .set(PunchUserConfig::getUserNftId, null)
                                .set(PunchUserConfig::getNextPunchTime, null)
                                .eq(PunchUserConfig::getId, config.getId())
                                .update();
                    }
                    break;
                case NORMAL:
                    break;
                default:
                    throw new ServiceException("状态异常:"+userNft.getNftStatus());
            }
        }
        this.lambdaUpdate()
                .eq(SysUserNft::getId, id)
                .set(SysUserNft::getIsLock, isLock)
                .set(SysUserNft::getNftStatus, isLock == 1 ? UserNftStatusEnum.LOCK : UserNftStatusEnum.NORMAL)
                .update();
    }

    /**
     * 校验用户是否拥有指定数量的藏品材料
     * @param userId
     * @param needMaterials
     */
    public boolean checkMaterials(Long userId, List<EquipmentUpgradeConfigBreak> needMaterials) {
        for (EquipmentUpgradeConfigBreak needMaterial : needMaterials) {
            Long count = getNftNumberCounts(needMaterial.getMaterialId(), userId);
            if (count < needMaterial.getNum()) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取用户背包的藏品
     *
     * @param userId
     * @param nftId
     * @return
     */
    public List<SysUserNft> getUserNftByUserIdAndNftId(Long userId, Long nftId) {
        return list(Wrappers.lambdaQuery(SysUserNft.class)
                .eq(SysUserNft::getUserId, userId)
                .eq(nftId != null, SysUserNft::getNftId, nftId)
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL));
    }

    /**
     * 获取用户背包的藏品 装备（未使用）
     *
     * @param userId
     * @return
     */
    public List<String> allNftNumberStr(Long userId, Long nftId) {
        List<SysUserNft> userNftList = getUserNftByUserIdAndNftId(userId, nftId);
        if (CollUtil.isNotEmpty(userNftList)) {
            List<Long> ids = userNftList.stream().map(SysUserNft::getNftNumberId).collect(Collectors.toList());
            List<NftNumber> nftNumbers = nftNumberService.listByIds(ids);
            return nftNumbers.stream().map(NftNumber::getNumberValueStr).collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

    /**
     * 藏品转赠
     *
     * @param userId
     * @param params
     */
    @Transactional(rollbackFor = Exception.class)
    public void transfer(Long userId, NftTransferDto params) {
        Nft nft = nftService.requireNft(params.getNftId());
        if (Objects.isNull(nft)) {
            throw new ServiceException("藏品不存在！");
        }
        // 验证用户是否拥有该nft 验证数量
        List<Long> nftNumberList = nftNumberService.getNftNumberIdsByNumberValueStr(params.getNftNumberStr());
        if (CollUtil.isEmpty(nftNumberList)) {
            throw new ServiceException("藏品编号不存在！");
        }
        List<SysUserNft> oldUserNftList = lambdaQuery()
                .eq(SysUserNft::getUserId, userId)
                .eq(SysUserNft::getNftId, params.getNftId())
                .eq(SysUserNft::getNftStatus, UserNftStatusEnum.NORMAL)
                .in(SysUserNft::getNftNumberId, nftNumberList)
                .list();
        if (oldUserNftList.size() < params.getNftNumberStr().size()) {
            throw new ServiceException("藏品编号数量不足！");
        }
        // 如果有热豆服务费扣除热豆
        if (Objects.nonNull(params.getHotBeans()) && params.getHotBeans().compareTo(BigDecimal.ZERO) > 0) {
            sysUserWalletService.reduce(userId, UserWalletEnum.INTEGRAL, params.getHotBeans(), WalletChangeTypeEnum.TRANSFER_DEPLETE, 0L, WalletChangeTypeEnum.TRANSFER_DEPLETE.getMsg());
        }
        // 如果有扳手服务费扣除扳手
        if (Objects.nonNull(params.getWrench()) && params.getWrench().compareTo(BigDecimal.ZERO) > 0) {
            userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.TRANSFER_DEPLETE, WalletChangeDirectionEnum.OUT, params.getWrench(), 0L, WalletWrenchChangeTypeEnum.TRANSFER_DEPLETE.getMsg());
        }
        // 如果有力豆服务费扣除力豆
        if (Objects.nonNull(params.getLidou()) && params.getLidou().compareTo(BigDecimal.ZERO) > 0) {
            userAssetLidouService.updateLidouBalance(userId, WalletLidouChangeTypeEnum.TRANSFER_DEPLETE, WalletChangeDirectionEnum.OUT, params.getLidou(), 0L, WalletLidouChangeTypeEnum.TRANSFER_DEPLETE.getMsg());
        }
        // 转赠id
        long giveId = IdUtil.getSnowflakeNextId();
        // 开始用户藏品转赠
        for (SysUserNft t : oldUserNftList) {
            moveUserNftToUser(t.getId(), userId, params.getToUserId(), 0L, UserNftSourceTypeEnum.TRANSFER, NftDispatchSourceEnum.TRANSFER, giveId);
        }
        // 保存转赠记录
        ConsumerTeamGiveRecord giveRecord = new ConsumerTeamGiveRecord();
        giveRecord.setId(giveId);
        giveRecord.setTeamId(0L);
        giveRecord.setGiveType(1);
        giveRecord.setGiveNum(params.getNftNumberStr().size());
        if (nft.getNftType() == NftTypeEnum.NORMAL) {
            giveRecord.setType(MarketTypeEnum.NORMAL);
        } else if (nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            giveRecord.setType(MarketTypeEnum.EQUIPMENT);
        }
        giveRecord.setGiveTime(new Date());
        giveRecord.setGiveUserId(userId);
        giveRecord.setReceiveUserId(params.getToUserId());
        giveRecord.setName(nft.getTitle());
        giveRecord.setMainImg(nft.getMainImg());
        giveRecord.setPublishName(nft.getPublishName());
        // 默认取第一个
        giveRecord.setNftNumberId(nftNumberList.get(0));
        // 设置转赠记录备注，将nftnumberId拼接为逗号隔开的字符串
        giveRecord.setGiveRemark(StrUtil.join(",", nftNumberList));
        consumerTeamGiveRecordService.save(giveRecord);
        // 存未读缓存
        redisCache.increment(RedisKey.KEY_USER_UNREAD_GIFT + params.getToUserId());
    }
}
